Skip to content

Support blocking socket ops via a single fd_wait primitive - #27342

Open
guybedford wants to merge 2 commits into
emscripten-core:mainfrom
guybedford:blocking-socket-ops
Open

Support blocking socket ops via a single fd_wait primitive#27342
guybedford wants to merge 2 commits into
emscripten-core:mainfrom
guybedford:blocking-socket-ops

Conversation

@guybedford

@guybedford guybedford commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Blocking accept/recv on sockets via a single _emscripten_fd_wait primitive, replacing the previous approach of marking each socket data syscall __async (which under JSPI wrapped every one in WebAssembly.Suspending, taxing every nonblocking call with a suspend/resume round-trip). The data syscalls stay strictly synchronous imports (single attempt, -EAGAIN on would-block); blocking is factored into one import and retry loops in the musl wrappers (accept, accept4, recvfrom, recvmsg).

  • pthreads-only: the retry loops compile only into the -mt libc (gated on __EMSCRIPTEN_PTHREADS__), and _emscripten_fd_wait blocks only by parking a proxied worker on its sync-proxy (PROXY_SYNC_ASYNC). Single-threaded ASYNCIFY/JSPI builds use epoll for readiness instead, so the synchronous path is byte-for-byte unchanged.
  • send/write never block (the Node.js backend buffers); read()/write() on a socket fd are not covered, only the socket calls.
  • accept4 now applies SOCK_NONBLOCK on top of the flags inherited from the listener.

Tested with test_noderawsockets_tcp_blocking (blocking accept + recv that must suspend, under PROXY_TO_PTHREAD) and test_noderawsockets_tcp_accept_nonblock, plus the mio suite under PROXY_TO_PTHREAD + NODERAWSOCKETS + NODERAWFS: 144 passed, 0 failed, 5 ignored.

Previous attempt in #27277.

Made with AI assistance under my review.

Blocking accept/recv on sockets, replacing the previous approach of marking
the socket data syscalls __async (which, under JSPI, wrapped every one of
them in WebAssembly.Suspending - taxing every nonblocking call with a
suspend/resume round-trip, since a Suspending import always resolves through
a Promise in V8).

The data syscalls stay strictly synchronous imports: single attempt, -EAGAIN
when they would block. Blocking is factored into one new import,
_emscripten_fd_wait(fd, events), and retry loops in the musl wrappers
(accept, accept4, recvfrom, recvmsg): on EAGAIN with a blocking fd and no
MSG_DONTWAIT, wait for readiness on the inode's listener queue and retry.

This is a pthreads-only facility. The retry loops compile only into the -mt
libc (gated on __EMSCRIPTEN_PTHREADS__ - the only compile-time boundary libc
has; ASYNCIFY is a link-time transform with no libc variant), and
_emscripten_fd_wait blocks only on a proxied pthread worker: __proxy sync +
__async gives the PROXY_SYNC_ASYNC call path, whose sync-proxy completes -
ending the worker's futex wait - when the returned Promise resolves. In every
other context, including the event-loop thread which cannot block, it fails
with -EAGAIN. Single-threaded ASYNCIFY/JSPI builds use epoll for readiness
instead, so a purely-synchronous build keeps the direct doReadv/doWritev path
byte-for-byte and hello-world code size is unchanged.

accept4 now applies SOCK_NONBLOCK to the accepted fd (on top of the flags it
inherits from the listener); without this a SOCK_NONBLOCK accept off a
blocking listener wrongly yielded a blocking socket.

Send/write paths are untouched: the node backend buffers and never
would-blocks, so blocking send degenerates to synchronous buffered success
and needs no wait machinery. read()/write() on a socket fd are likewise not
covered - only the socket calls themselves.

Tested with test_noderawsockets_tcp_blocking (blocking accept + recv that must
suspend, under PROXY_TO_PTHREAD) and test_noderawsockets_tcp_accept_nonblock
(accept4 SOCK_NONBLOCK off a blocking listener), plus the mio test suite under
PROXY_TO_PTHREAD + NODERAWSOCKETS + NODERAWFS: 144 passed, 0 failed, 5 ignored.
@guybedford
guybedford force-pushed the blocking-socket-ops branch from c6bfc66 to 9e2c0d8 Compare August 18, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant